/**
 * js 中的原始类型：
 *  1. string
 *  2. number
 *  3. boolean
 *  4. null
 *  5. undefined
 *  6. bigint
 *  7. symbol
 * 原始类型的值本身是没有任何属性和方法。
 *
 * js 中的包装类型:
 *  1. String
 *  2. Number
 *  3. Boolean
 *  4. BigInt
 *  5. Symbol
 */

// 1. Boolean 类型
const isDone: boolean = false

// 2. Number 类型
// 和 JavaScript 一样，TypeScript 中的所有数字都是浮点数/大整数
// 浮点数的类型是 number，大整数的类型是 bigint。
let a: number = 6
let b: number = 0b1011
let c: number = 0o744
let d: Number = new Number(0xf00d) // 这样返回的就是包装类型了
let e: bigint = BigInt('100') // es2020 支持 BigInt('100') 返回的结果是原始类型 bigint
console.log('a = ', a)
console.log('b = ', b)
console.log('c = ', c)
console.log('d = ', d)
console.log('e = ', e)

// typeof 会返回一个字符串
// typeof new String('1') 这会返回 object, 注意避免这样使用
console.log("typeof a === 'number'?", typeof a === 'number')

// 3. String 类型
const name1: string = 'zs'
console.log(typeof name1) // string
const name2: String = new String('zs')
console.log(typeof name2) // object

// 也可以使用模板字符串
const msg = `Hello ${name1}, it's fine.`
console.log(msg)

/**
 * 4. Array
 * TypeScript像JavaScript一样可以操作数组元素。
 * 有两种方式可以定义数组:
 *  1. 可以在元素类型后面接上[]。
 *  2. 使用数组泛型 Array<T>。
 */
const arr1: string[] = ['1', '2', '3']
const arr2: Array<string> = ['1', '2', '3']
console.log('arr1 =', arr1, 'arr2 =', arr2)
console.log(arr1 === arr2) // false

// 5. 元组 Tuple 类型
// 元组类型允许表示一个已知元素数量和类型的数组，各元素的类型不必相同。
const tup1: [string, number, boolean, number] = ['1', 1, false, 3]
console.log(tup1) // [ '1', 1, false, 3 ]

// 语法错误 const tup2: [string, number, boolean, number] = ['1', 1, false, 3， 8]

// 这个元组其实就是一个 Array
tup1.push('a')
console.log(tup1) // [ '1', 1, false, 3, 'a' ]

// 访问元组, 元组下标越界会报错
console.log(tup1[1]) // 1

// 6. enum 枚举
// enum类型是对JavaScript标准数据类型的一个补充。

// 默认情况下, 枚举从 0 开始为元素编号
enum Color {
  Red,
  Green,
  Blue
}

const col: Color = Color.Blue
console.log(col) // 2

// 手动为枚举元素编号

enum Fruit {
  Apple = 1
}
console.log(Fruit.Apple) // 1
console.log(Fruit[1]) // Apple
console.log(typeof Fruit[1]) // string

/**
 * 7. Unknown
 * 当我们在写应用的时候可能会需要描述一个我们还不知道其类型的变量。
 * 这些值可以来自动态内容，例如从用户获得，或者我们想在我们的 API 中接收所有可能类型的值。
 * 在这些情况下，我们想要让编译器以及未来的用户知道这个变量可以是任意类型。
 * 这个时候我们会对它使用 unknown 类型。
 */
let notSure: unknown = 4
notSure = 'hello'
console.log(notSure)

// 8. void
// 当一个函数没有返回值时，你通常会见到其返回值类型是void
function print1(str: string): void {
  console.log('传入的字符串为:', str)
}

print1('hello')

// 9. Null 和 Undefined
// TypeScript里，undefined和null两者各自有自己的类型分别叫做undefined和null。
// 和void相似，它们的本身的类型用处不是很大。
let a1: number

// undefined 是原始数据类型, 不是包装数据类型, 所以直接用 "===" 即可
if (a1 === undefined) {
  console.log('undefined')
  console.log('typeof a1 =', typeof a1)
}

// 10. never类型表示的是那些永不存在的值的类型
// 例如，never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型；
// 变量也可能是never类型，当它们被永不为真的类型保护所约束时。

// 返回never的函数必须存在无法达到的终点
function err(msg: string): never {
  throw new Error(msg)
}

// 11. object
// object表示非原始类型，也就是除number，string，boolean，bigint，symbol，null或undefined之外的类型。
function create(obj: object | number[]): void {
  console.log(obj)
}

create({ name: 'zs', age: 18 })
create([1, 2, 3])

// 12. 类型断言（类型转换）
let someValue: any = 'this is a string.'
console.log(typeof someValue) // string

// 类型转换有两种形式
let strLen1: number = (<string>someValue).length
let strLen2: number = (someValue as string).length
console.log('strLen1 =', strLen1, 'strLen2 =', strLen2)

// 13. 关于 Number, String, Boolean, Symbol 和 Object
/* 
  我们很容易会认为 Number、 String、 Boolean、Symbol 以及 Object 这些类型和我们以上推荐的小写版本的类型是一样的。
  但这些类型不属于语言的基本类型，并且几乎在任何时候都不应该被用作一个类型。
  所以，我们应该使用 number、string、boolean、object 和 symbol.
*/
function reverse(str: string): string {
  return Array.from(str).reverse().join()
}

console.log(reverse('Hello'));
